package com.demo.websocket.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/{sid}")
public class WebSocketServer {

    private static AtomicInteger onlineSessionCount = new AtomicInteger(0); // 当前在线连接数

    /**
     * 在线连接数:在线的客户端
     */
    private static ConcurrentMap<String, Session> onlineSessionMap = new ConcurrentHashMap<>();

    /**
     * 会话ID
     */
    private String sid;
    /**
     * 会话
     */
    private Session session;

    /**
     * 连接建立成功调用的方法。由前端<code>new WebSocket</code>触发
     *
     * @param sid     每次页面建立连接时传入到服务端的id，比如用户id等。可以自定义。
     * @param session 与某个客户端的连接会话，需要通过它来给客户端发送消息
     */
    @OnOpen
    public void onOpen(@PathParam("sid") String sid, Session session) {
        log.info("连接建立中===》session_id={},sid={}", session.getId(), sid);
        onlineSessionMap.put(sid, session);//2. 保存会话
        //在线数加1
        onlineSessionCount.incrementAndGet();
        this.sid = sid;
        this.session = session;
        log.info("连接建立成功,当前在线数为：{}===>开始监听新连接:session_id={},sid={}", onlineSessionCount, session.getId(), sid);
    }

    /**
     * 连接关闭调用的方法。由前端<code>socket.close()</code>触发
     *
     * @param sid
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid, Session session) {
        log.info("连接关闭中===》session_id={},sid={}", session.getId(), sid);
        onlineSessionMap.remove(sid);
        //在线数减1
        onlineSessionCount.decrementAndGet();
        log.info("连接关闭成功,当前在线数为：{}===>关闭的连接为:session_id={},sid={}", onlineSessionCount, session.getId(), sid);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        /**
         * html界面传递来得数据格式，可以自定义.
         * {"sid":"user-1","message":"hello websocket"}
         */
        JSONObject jsonObject = JSON.parseObject(message);
        String toSid = jsonObject.getString("sid");
        String msg = jsonObject.getString("message");
        log.info("服务端收到客户端消息 ==> fromSid = {}, toSid = {}, message = {}", sid, toSid, message);

        /**
         * 模拟约定：如果未指定sid信息，则群发，否则就单独发送
         */
        if (toSid == null || toSid.isEmpty()) {
            sendToAll(msg);
        } else {
            sendToOne(toSid, msg);
        }
    }

    /**
     * 单独发送
     * @param toSid
     * @param msg
     */
    private void sendToOne(String toSid, String msg) {
        Session toSession = onlineSessionMap.get(toSid);
        if (toSession == null) {
            log.error("服务端给客户端发送消息 ==> toSid = {} 不存在或不在线, message = {}", toSid, msg);
            return;
        }
        // 异步发送
        log.info("服务端给客户端发送消息 ==> toSid = {}, message = {}", toSid, msg);
        toSession.getAsyncRemote().sendText(msg);
    }

    /**
     * 群发
     * @param msg
     */
    private void sendToAll(String msg) {
        onlineSessionMap.forEach((onlineSid, toSession) -> {
            //排除自己
            if (!sid.equals(onlineSid)) {
                log.info("服务端给客户端群发消息 ==> sid = {}, toSid = {}, message = {}", sid, onlineSid, msg);
                toSession.getAsyncRemote().sendText(msg);
            }
        });
    }

    /**
     * 发生错误调用的方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket发生错误，错误信息为：" + error.getMessage());
        error.printStackTrace();
    }
}